/*
 * Copyright (C) 2010 Graham Allan
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.strath.cis.grallan.findbugs.adaptiveranking.gui;

import java.awt.Container;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.concurrent.Executor;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;

import edu.strath.cis.grallan.findbugs.adaptiveranking.AdaptiveRankBug;
import edu.strath.cis.grallan.findbugs.adaptiveranking.AdaptiveRankedBugList;
import edu.strath.cis.grallan.findbugs.adaptiveranking.AdaptiveRanker;
import edu.strath.cis.grallan.findbugs.adaptiveranking.DisplayUnclassifiedIssues;
import edu.strath.cis.grallan.findbugs.adaptiveranking.RankingProgressCallback;
import edu.strath.cis.grallan.findbugs.adaptiveranking.RankingStateListener;
import edu.strath.cis.grallan.findbugs.adaptiveranking.SwingRankingMode;
import edu.strath.cis.grallan.findbugs.adaptiveranking.gui.IRankingViewController.DisplayState;
import edu.strath.cis.grallan.findbugs.adaptiveranking.gui.RankingIconFactory.RankingIcons;
import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.gui2.ApplicationDisplayIssueDecider;
import edu.umd.cs.findbugs.gui2.BugSet;
import edu.umd.cs.findbugs.gui2.BugSetFactory;
import edu.umd.cs.findbugs.gui2.BugTreeModel;
import edu.umd.cs.findbugs.gui2.DisplayIssueDecider;
import edu.umd.cs.findbugs.gui2.ITreeHolder;
import edu.umd.cs.findbugs.gui2.sort.SortableBugTree;

/**
 * Base implementation for SwingAdaptiveRankingModes. <br>
 * <br>
 * Provides components and actions which are likely to be useful for ranking modes, such as
 * calculating the ranking, refreshing, and filtering classified bugs.
 * 
 * @author Graham Allan (grallan@cis.strath.ac.uk)
 */
public abstract class AbstractSwingRankingMode<T extends AdaptiveRankBug<?>> extends AbstractRankingMode implements
		SwingRankingMode, RankingProgressCallback, RankingStateListener, ITreeHolder {

	protected final IRankingViewController viewController;
	protected final BugSetFactory bugSetFactory;
	protected final DisplayIssueDecider unclassifiedDisplayer;
	protected final DisplayIssueDecider allButDeadDisplayer;
	protected final RankedBugTree tree;
	protected final AdaptiveRanker<T> ranker;
	protected final Executor rankingExecutor;
	protected final Container containerForMode;

	protected final CalculateAction calculateAction = new CalculateAction();
	protected final RefreshAction refreshAction = new RefreshAction();
	protected FilterToggleAction toggleFilterAction;
	protected final HelpAction helpAction = new HelpAction();
	protected final AutoRefreshToggleAction toggleAutoRefreshAction = new AutoRefreshToggleAction();

	private final HelpContainerProvider helpProvider = new HelpContainerProvider();

	public AbstractSwingRankingMode(String name, String label, IRankingModeHelpInfo helpInfo,
			AdaptiveRanker<T> adaptiveRanker, RankedBugTree tree, IRankingViewController viewController,
			BugSetFactory bugSetFactory, Executor rankingExecutor,
			ApplicationDisplayIssueDecider applicationDisplayDeciderPrototype) {
		super(name, label, helpInfo);
		this.ranker = adaptiveRanker;
		this.tree = tree;
		this.viewController = viewController;
		this.bugSetFactory = bugSetFactory;
		this.rankingExecutor = rankingExecutor;

		unclassifiedDisplayer = applicationDisplayDeciderPrototype.cloneWithNoChainedDeciders().chainDecider(
				new DisplayUnclassifiedIssues());
		allButDeadDisplayer = applicationDisplayDeciderPrototype.cloneWithNoChainedDeciders().chainDecider(
				DisplayIssueDecider.DISPLAY_ALL_BUT_DEAD);

		this.containerForMode = makeContainerForMode();
	}

	protected abstract Container makeContainerForMode();

	protected void setRankingOrder() {
		BugSet bugSet = rebuildBugSet();
		changeBugSet(bugSet);
	}

	protected BugSet rebuildBugSet() {
		AdaptiveRankedBugList<?> learningOrder = getRankedList();
		BugCollection orderedBugs = learningOrder.asBugCollection();
		BugSet bugSet = bugSetFactory.createBugSet(orderedBugs);
		return bugSet;
	}

	/**
	 * Can be overridden by subclasses to modify the list returned by the adaptive ranker. <br>
	 * <br>
	 * For example, to create a sub list of the total amount of bugs. <br>
	 * <br>
	 * Default implementation is to use the entire list of bugs returned by
	 * <code>this{@link #ranker#getRankedList()}
	 * <br><br>
	 */
	protected AdaptiveRankedBugList<T> getRankedList() {
		AdaptiveRankedBugList<T> learningOrder = ranker.getRankedList();
		return learningOrder;
	}

	protected void changeBugSet(BugSet bugSet) {
		BugTreeModel treeModel = (BugTreeModel) tree.getModel();
		BugTreeModel newModel = treeModel.cloneWithDifferentBugSet(bugSet, true);

		tree.setModel(newModel);
		tree.expandToAndSelectFirstLeaf(Integer.MAX_VALUE);
		tree.requestFocusInWindow();

	}

	protected JToggleButton makeToggleFilterButton() {
		bugSetFactory.setFilterDecider(unclassifiedDisplayer);
		toggleFilterAction = new FilterToggleAction(bugSetFactory);
		toggleFilterAction.putValue(Action.SELECTED_KEY, Boolean.TRUE);
		JToggleButton toggleFilterButton = new JToggleButton(toggleFilterAction);
		toggleFilterButton.setToolTipText("Filter out the bugs you've already inspected");
		setIcon(toggleFilterButton, RankingIcons.FILTER);

		return toggleFilterButton;
	}

	protected JButton makeRefreshButton() {
		JButton refreshButton = new JButton();
		refreshButton.setAction(refreshAction);
		refreshButton.setToolTipText("Refresh the ranking of bugs");
		setIcon(refreshButton, RankingIcons.REFRESH);
		refreshButton.setMnemonic('R');
		return refreshButton;
	}

	protected JButton makeCalculateButton() {
		JButton calculateButton = new JButton(calculateAction);
		calculateButton.setToolTipText("Calculate the ranking of all bug alerts");
		setIcon(calculateButton, RankingIcons.CALCULATE);
		calculateButton.setMnemonic('C');
		return calculateButton;
	}

	protected JToggleButton makeHelpButton() {
		JToggleButton helpButton = new JToggleButton(helpAction);
		helpButton.setToolTipText("Show / hide help information");
		setIcon(helpButton, RankingIcons.HELP);
		return helpButton;
	}

	protected JToggleButton makeAutoRefreshButton() {
		JToggleButton autoRefreshButton = new JToggleButton(toggleAutoRefreshAction);
		autoRefreshButton.setToolTipText("Automatically refresh the ranking order after designating a bug");
		setIcon(autoRefreshButton, RankingIcons.AUTO_REFRESH);
		return autoRefreshButton;
	}

	protected JTextComponent makeHelpTextComponent() {
		return helpProvider.getContainerForHelp(this.helpInfo());
	}

	private void setIcon(AbstractButton toggleFilterButton, RankingIcons rankingIcon) {
		try {
			Icon imageIcon = new RankingIconFactory().getIcon(rankingIcon);
			toggleFilterButton.setIcon(imageIcon);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	protected JScrollPane makeTreePane() {
		JScrollPane scrollPane = new JScrollPane(tree);
		scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		return scrollPane;
	}

	protected class RefreshAction extends AbstractAction {
		public RefreshAction() {
			super("Refresh");
		}

		public void actionPerformed(ActionEvent e) {
			class RefreshRunnable implements Runnable {
				public void run() {
					doRefresh();
				}
			}
			rankingExecutor.execute(new RefreshRunnable());
		}
	}

	protected abstract void doRefresh();

	protected class CalculateAction extends AbstractAction {
		public CalculateAction() {
			super("Calculate");
		}

		public void actionPerformed(ActionEvent e) {
			startRankingThread();
		}
	}

	protected class FilterToggleAction extends AbstractAction {

		private final BugSetFactory bugSetFactory;

		public FilterToggleAction(BugSetFactory bugSetFactory) {
			super("Filter Classified");
			this.bugSetFactory = bugSetFactory;
		}

		public void actionPerformed(ActionEvent e) {
			JToggleButton toggleButton = (JToggleButton) e.getSource();
			if (toggleButton.isSelected()) {
				bugSetFactory.setFilterDecider(unclassifiedDisplayer);
			} else {
				bugSetFactory.setFilterDecider(allButDeadDisplayer);
			}

			viewController.hideView();
			class FilterChangeRebuild implements Runnable {
				public void run() {
					setRankingOrder();
					viewController.showView();

				}
			}
			SwingUtilities.invokeLater(new FilterChangeRebuild());
		}
	}

	protected class AutoRefreshToggleAction extends AbstractAction {

		private boolean isSelected;

		public AutoRefreshToggleAction() {
			super("Auto-Refresh");
		}

		public void actionPerformed(ActionEvent e) {
			JToggleButton toggleButton = (JToggleButton) e.getSource();
			isSelected = toggleButton.isSelected();
		}

		public boolean autoRefreshSelected() {
			return isSelected;
		}

	}

	protected class HelpAction extends AbstractAction {

		private boolean selected = false;

		public HelpAction() {
			super("Help");
		}

		public void actionPerformed(ActionEvent e) {
			JToggleButton toggleButton = (JToggleButton) e.getSource();
			selected = toggleButton.isSelected();
			doHelpAction();
		}

		public boolean helpSelected() {
			return selected;
		}
	}

	protected abstract void doHelpAction();

	public void setRebuilding(boolean freezeUi) {
		if (freezeUi) {
			viewController.hideView();
		} else {
			viewController.showView();
		}
	}

	public void newTree(SortableBugTree newTree, BugTreeModel newModel) {
		if (ranker.rankingComputed()) {
			setRankingOrder();
		}
	}

	public Container containerForMode() {
		return containerForMode;
	}

	protected void startRankingThread() {
		class CalculationRunnable implements Runnable {
			public void run() {
				doCalculate();
			}
		}

		rankingExecutor.execute(new CalculationRunnable());
	}

	protected abstract void doCalculate();

	public void notifyCanRefresh() {
		refreshAction.setEnabled(true);
		doAutoRefreshIfRequired();
	}

	private void doAutoRefreshIfRequired() {
		if (toggleAutoRefreshAction.autoRefreshSelected()) {
			refreshAction.actionPerformed(null);
		}
	}

	protected void doClickSelectedBug() {
		if (viewController.isDisplayedMode(this) == DisplayState.DISPLAYED) {
			tree.doSelectionClick();
		}
	}

	public SortableBugTree getTree() {
		return this.tree;
	}

	public BugTreeModel getBugTreeModel() {
		return (BugTreeModel) this.tree.getModel();
	}

}